package emongodb

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"git.edaijia-inc.cn/edaijia/ego/elogger"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type EMongoDbConfig struct {
	User        string   `ini:"user"`
	Password    string   `ini:"password"`
	DataSource  []string `ini:"dataSource"`
	AuthDB      string   `ini:"authDb"`
	DB          string   `ini:"db"`
	ReplicaSet  string   `ini:"replicaSet"`
	MaxPoolSize uint64   `ini:"maxPoolSize"`
}

type EMongoDb struct {
	Config *EMongoDbConfig
	Client *mongo.Client
	ctx    context.Context
	Db     string
}

type Filter struct {
	Key   string
	Value interface{}
}

type ctxSpanKey struct{}

func NewMasterEMongoDb(conf *EMongoDbConfig) (*EMongoDb, error) {
	return newEMongoDb(conf, "primary")
}

func NewQueryEMongoDb(conf *EMongoDbConfig) (*EMongoDb, error) {
	return newEMongoDb(conf, "secondaryPreferred")
}

func newEMongoDb(conf *EMongoDbConfig, readPreference string) (*EMongoDb, error) {
	var uri string
	if len(conf.User) > 0 {
		uri = fmt.Sprintf("mongodb://%s:%s@%s/%s?", conf.User, conf.Password, strings.Join(conf.DataSource, ","), conf.AuthDB)
	} else {
		uri = fmt.Sprintf("mongodb://%s/%s?", strings.Join(conf.DataSource, ","), conf.AuthDB)
	}
	if readPreference != "" {
		uri = fmt.Sprintf("%sreadPreference=%s&", uri, readPreference)
	}
	if conf.ReplicaSet != "" {
		uri = fmt.Sprintf("%sreplicaSet=%s&", uri, conf.ReplicaSet)
	}
	uri = strings.TrimRight(strings.TrimRight(uri, "?"), "&")
	client, err := mongo.NewClient(options.Client().ApplyURI(uri).SetMaxPoolSize(conf.MaxPoolSize))
	if err != nil {
		elogger.Error("NewEMongoDb,连接mongo出错,err=", err.Error())
		return nil, err
	}
	timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	err = client.Connect(timeout)
	if err != nil {
		elogger.Error("NewEMongoDb,连接mongoConnect出错,err=", err.Error())
		return nil, err
	}
	err = client.Ping(timeout, nil)
	if err != nil {
		elogger.Error("NewEMongoDb,连接mongoConnect出错,err=", err.Error())
		return nil, err
	}
	return &EMongoDb{Config: conf, Client: client, ctx: timeout, Db: conf.DB}, nil
}

func (e *EMongoDb) connect(ctx context.Context, collection string) *mongo.Collection {
	sessionContext := context.Background()
	//if ctx != nil {
	//	span := etracer.EMysqlSpanStart(ctx, strings.Join(e.Config.DataSource, ","), "Mongo/EGO/emongo")
	//	if span != nil {
	//		sessionContext = context.WithValue(sessionContext, ctxSpanKey{}, span)
	//	}
	//}
	e.ctx = sessionContext
	c := e.Client.Database(e.Db).Collection(collection)
	return c
}

//FindAll 查询多个
func (e *EMongoDb) FindAll(ctx context.Context, collection string, filter []Filter, limit int64, result interface{}) error {
	sort := bson.D{{"_id", 1}}
	s := e.connect(ctx, collection)
	findOptions := options.Find()
	findOptions.SetLimit(limit)
	findOptions.SetSort(sort)
	var d bson.D
	if len(filter) > 0 {
		for _, f := range filter {
			var e bson.E
			e.Key = f.Key
			e.Value = f.Value
			d = append(d, e)
		}
	} else {
		d = bson.D{{}}
	}
	find, err := s.Find(e.ctx, d, findOptions)
	if err != nil {
		return err
	}
	defer find.Close(e.ctx)
	var resultDataArr []map[string]interface{}
	for find.Next(e.ctx) {
		one := make(map[string]interface{})
		err := find.Decode(&one)
		if err != nil {
			return err
		}
		resultDataArr = append(resultDataArr, one)
	}
	marshal, err := json.Marshal(resultDataArr)
	if err != nil {
		return err
	}
	err = json.Unmarshal(marshal, &result)
	if err != nil {
		return err
	}
	return nil
}

//FindOne 查询单个
func (e *EMongoDb) FindOne(ctx context.Context, collection string, filter Filter, result interface{}) error {
	s := e.connect(ctx, collection)
	filterD := bson.D{{filter.Key, filter.Value}}
	find := s.FindOne(e.ctx, filterD)
	if find != nil {
		err := find.Decode(result)
		if err != nil {
			return err
		}
	} else {
		return errors.New("未找到记录")
	}
	return nil
}

//Count 查询count总数
func (e *EMongoDb) Count(ctx context.Context, collection string, lng, lat float64, serviceType string, driverWorkType int, cityId, status int, appVer string, maxDistance float64, limit int64, driverLabel, statusStr, lastUpdateTime string) (int64, error) {
	s := e.connect(ctx, collection)
	l := bson.D{}
	filter := e.buildFilter(serviceType, driverWorkType, cityId, status, appVer, limit, driverLabel, statusStr, lastUpdateTime)
	if lng > 0 && lat > 0 && maxDistance > 0 {
		radius := maxDistance / 6378000.0 // 半径（单位：弧度）

		l = bson.D{
			{"location", bson.D{
				{"$geoWithin", bson.D{
					{"$centerSphere", bson.A{
						bson.A{lng, lat},
						radius,
					}},
				}},
			}},
		}
		filter = append(filter, l...)
	}
	elogger.Info("emongodb,Count,l:", filter)
	size, err := s.CountDocuments(e.ctx, filter)
	return size, err

}

//InsertOne 插入单个
func (e *EMongoDb) InsertOne(ctx context.Context, collection string, document interface{}) (*mongo.InsertOneResult, error) {
	s := e.connect(ctx, collection)
	insertResult, err := s.InsertOne(e.ctx, document)
	return insertResult, err
}

//InsertMany 插入多个
func (e *EMongoDb) InsertMany(ctx context.Context, collection string, documents []interface{}) (*mongo.InsertManyResult, error) {
	s := e.connect(ctx, collection)
	insertResult, err := s.InsertMany(e.ctx, documents)
	return insertResult, err
}

//Delete 删除
func (e *EMongoDb) Delete(ctx context.Context, collection string, filter Filter) (int64, error) {
	filterD := bson.D{{filter.Key, filter.Value}}
	s := e.connect(ctx, collection)
	count, err := s.DeleteOne(e.ctx, filterD, nil)
	return count.DeletedCount, err

}

//DeleteMany 删除多个
func (e *EMongoDb) DeleteMany(ctx context.Context, collection string, filter Filter) (int64, error) {
	filterD := bson.D{{filter.Key, filter.Value}}
	s := e.connect(ctx, collection)
	count, err := s.DeleteMany(e.ctx, filterD)
	return count.DeletedCount, err
}

//UpdateOne 更新一个
func (e *EMongoDb) UpdateOne(ctx context.Context, collection string, filter []Filter, update []Filter) (*mongo.UpdateResult, error) {
	var selector bson.D
	if len(filter) > 0 {
		for _, f := range filter {
			selector = append(selector, bson.E{Key: f.Key, Value: f.Value})
		}
	}
	var updateD bson.D
	if len(update) > 0 {
		for _, u := range update {
			updateD = append(updateD, bson.E{Key: u.Key, Value: u.Value})
		}
	}
	s := e.connect(ctx, collection)
	updateResult, err := s.UpdateOne(e.ctx, selector, bson.D{{"$set", updateD}})
	return updateResult, err
}

//UpdateMany 更新多个
func (e *EMongoDb) UpdateMany(ctx context.Context, collection string, filter []Filter, update []Filter) (*mongo.UpdateResult, error) {
	var selector bson.D
	if len(filter) > 0 {
		for _, f := range filter {
			selector = append(selector, bson.E{Key: f.Key, Value: f.Value})
		}
	}
	var updateD bson.D
	if len(update) > 0 {
		for _, u := range update {
			updateD = append(updateD, bson.E{Key: u.Key, Value: u.Value})
		}
	}
	s := e.connect(ctx, collection)
	updateResult, err := s.UpdateMany(e.ctx, selector, bson.D{{"$set", updateD}})
	return updateResult, err
}

//UpdateByPk 主键更新
func (e *EMongoDb) UpdateByPk(ctx context.Context, collection string, id interface{}, update []Filter) (*mongo.UpdateResult, error) {
	var updateD bson.D
	if len(update) > 0 {
		for _, u := range update {
			updateD = append(updateD, bson.E{Key: u.Key, Value: u.Value})
		}
	}
	s := e.connect(ctx, collection)
	byID, err := s.UpdateByID(e.ctx, id, bson.D{{"$set", updateD}})
	return byID, err
}

//CloseConnect 关闭链接
func (e *EMongoDb) CloseConnect(ctx context.Context) error {
	return e.Client.Disconnect(ctx)
}

//QueryByLocation 根据经纬度查询
func (e *EMongoDb) QueryByLocation(ctx context.Context, collection string, lng, lat float64, serviceType string, driverWorkType int, cityId, status int, appVer string, maxDistance float64, limit int64, driverLabel, statusStr, lastUpdateTime string, result interface{}) error {
	s := e.connect(ctx, collection)
	findOptions := options.Find()
	findOptions.SetLimit(limit)
	filter := e.buildFilter(serviceType, driverWorkType, cityId, status, appVer, limit, driverLabel, statusStr, lastUpdateTime)
	if lng > 0 && lat > 0 && maxDistance > 0 {
		l := bson.D{
			{Key: "location", Value: bson.D{
				{"$near", []float64{lng, lat}},
				{"$maxDistance", maxDistance},
			}},
		}
		filter = append(filter, l...)
	}
	elogger.Info("emongodb,QueryByLocation,l:", filter)
	find, err := s.Find(e.ctx, filter, findOptions)
	if err != nil {
		return err
	}
	defer find.Close(e.ctx)
	var resultDataArr []map[string]interface{}
	for find.Next(e.ctx) {
		one := make(map[string]interface{})
		err := find.Decode(&one)
		if err != nil {
			return err
		}
		resultDataArr = append(resultDataArr, one)
	}
	marshal, err := json.Marshal(resultDataArr)
	if err != nil {
		return err
	}
	err = json.Unmarshal(marshal, &result)
	if err != nil {
		return err
	}
	return nil
}

func (e *EMongoDb) buildFilter(serviceType string, driverWorkType int, cityId, status int, appVer string, limit int64, driverLabel, statusStr, lastUpdateTime string) bson.D {
	l := bson.D{}

	if len(serviceType) > 0 {
		var e bson.E
		e.Key = "service_type"
		serviceType = strings.ReplaceAll(serviceType, "0", ".")
		e.Value = bson.M{"$regex": serviceType, "$options": "$i"}
		l = append(l, e)
	}

	if len(statusStr) > 0 {
		var e bson.E
		statusIntArr := []int{}
		statusArr := strings.Split(statusStr, ",")
		for _, v := range statusArr {
			statusInt, _ := strconv.Atoi(v)
			statusIntArr = append(statusIntArr, statusInt)
		}
		e.Key = "status"
		e.Value = bson.M{"$in": statusIntArr}
		l = append(l, e)
	} else if status != -1 {
		var e bson.E
		e.Key = "status"
		e.Value = status
		l = append(l, e)
	}

	if driverWorkType > 0 && driverWorkType != -1 {
		var e bson.E
		e.Key = "driver_work_type"
		e.Value = driverWorkType
		l = append(l, e)
	}
	if cityId > 0 {
		var e bson.E
		e.Key = "work_city_ids"
		e.Value = strconv.Itoa(cityId)
		l = append(l, e)
	}
	if len(driverLabel) > 0 {
		//适配多个司机标签
		driverLabelArr := strings.Split(driverLabel, ",")
		if len(driverLabelArr) > 1 {
			var e bson.E
			e.Key = "driver_label"
			e.Value = bson.M{"$in": driverLabelArr}
			l = append(l, e)
		} else {
			var e bson.E
			e.Key = "driver_label"
			e.Value = driverLabel
			l = append(l, e)
		}
	}
	if len(appVer) > 0 {
		var e bson.E
		e.Key = "app_ver"
		e.Value = bson.D{{"$gte", appVer}}
		l = append(l, e)
	}

	if len(lastUpdateTime) > 0 { //新增过滤update 参数 ， eg: 最后更新时间在15分内的数据
		var e bson.E
		e.Key = "update"
		e.Value = bson.D{{"$gte", lastUpdateTime}}
		l = append(l, e)
	}
	return l
}
